package mylab.base.foundation.orm.ability;

import com.google.common.collect.Lists;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;

import cn.hutool.core.lang.func.VoidFunc1;
import cn.hutool.db.Db;
import cn.hutool.db.DbUtil;
import cn.hutool.db.Entity;
import cn.hutool.db.handler.BeanListHandler;
import cn.hutool.db.handler.EntityListHandler;
import cn.hutool.db.handler.RsHandler;
import cn.hutool.db.meta.MetaUtil;
import cn.hutool.db.meta.Table;
import cn.hutool.db.sql.SqlExecutor;
import mylab.utils.common.StringUtil;

public interface DsAdmin extends DS {
    String CREATE_DATABASE_SQL = "CREATE DATABASE {} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;";
    String DROP_DATABASE_SQL = "DROP DATABASE IF EXISTS {};";
    String DROP_TABLE_SQL = "DROP TABLE IF EXISTS {};";

    default void createDatabase(String dbName) throws SQLException {
        String sql = StringUtil.format(CREATE_DATABASE_SQL, dbName);
        getDb().execute(sql);
    }

    default void dropDatabase(String dbName) throws SQLException {
        String sql = StringUtil.format(DROP_DATABASE_SQL, dbName);
        getDb().execute(sql);
    }

    default void createTable(String sql) throws SQLException {
        getDb().execute(sql);
    }

    default void dropTable(String tableName) throws SQLException {
        String sql = StringUtil.format(DROP_TABLE_SQL, tableName);
        getDb().execute(sql);
    }

    default List<String> getTables() {
        return MetaUtil.getTables(getDataSource());
    }

    default Table getTableInfo(String tableName) {
        return MetaUtil.getTableMeta(getDataSource(), tableName);
    }

    default int execute(String sql, Object... params) throws SQLException {
        return getDb().execute(sql, params);
    }

    default List<Entity> select(String sql, Object... params) throws Exception {
        AtomicReference<List<Entity>> resultRef = new AtomicReference<>(Lists.newArrayList());
        sqlExecutor(connection -> {
            try {
                List<Entity> list = SqlExecutor.query(connection, sql, new EntityListHandler(), params);
                resultRef.set(list);
            } catch (SQLException e) {
                return e;
            }
            return null;
        });
        return resultRef.get();
    }

    default <T> List<T> select(String sql, Class<T> entityClass, Object... params) throws Exception {
        AtomicReference<List<T>> resultRef = new AtomicReference<>(Lists.newArrayList());
        sqlExecutor(connection -> {
            try {
                RsHandler<List<T>> rsHandler = BeanListHandler.create(entityClass);
                List<T> list = SqlExecutor.query(connection, sql, rsHandler, params);
                resultRef.set(list);
            } catch (SQLException e) {
                return e;
            }
            return null;
        });
        return resultRef.get();
    }

    default void txInsert(Entity entity) throws Exception {
        getDb().tx((VoidFunc1<Db>) db -> {
            db.insert(entity);
        });
    }

    default void txUpdate(Entity entity, Entity where) throws Exception {
        getDb().tx((VoidFunc1<Db>) db -> {
            db.update(entity, where);
        });
    }

    default void txDelete(Entity where) throws Exception {
        getDb().tx((VoidFunc1<Db>) db -> {
            db.del(where);
        });
    }

    default void tx(Function<Db, Exception> function) throws SQLException {
        getDb().tx((VoidFunc1<Db>) db -> {
            Exception exception = function.apply(db);
            if (exception != null) {
                throw exception;
            }
        });
    }

    default void sqlExecutor(Function<Connection, Exception> function) throws Exception {
        Connection conn = null;
        try {
            conn = getConnection();
            Exception exception = function.apply(conn);
            if (exception != null) {
                throw exception;
            }
        } finally {
            DbUtil.close(conn);
        }
    }

    static void executor(Db db, Function<Connection, Exception> function) throws Exception {
        Connection conn = null;
        try {
            conn = db.getConnection();
            Exception exception = function.apply(conn);
            if (exception != null) {
                throw exception;
            }
        } finally {
            DbUtil.close(conn);
        }
    }

    static void tx(Db db, Function<Db, Exception> function) throws SQLException {
        db.tx((VoidFunc1<Db>) parameter -> {
            Exception exception = function.apply(parameter);
            if (exception != null) {
                throw exception;
            }
        });
    }

}
